// --- 2D ---
//* printf text
//* moveto lineto

// --- 3D ---
//* drawspr("cow.kv6",x,y,z,hang,vang,tilt)
//* drawmodel("apple.obj") // support .obj .x .3ds .md2
//* [x] obj loader
//* drawmodel(filename:$, castShadow:bool, recvShadow:bool)
//* drawmodel("wizard.md2", "walk", t) // draw animated model, specify animation in file and anim position t [0,1]
//* drawcone 3d
//* drawsph
//* drawcube mtx
//* enableFrustumCulling
//* glBegin(GLenum mode))
//* gltexcoord
//* glnormal
//* setcamera

// --- imgui ---
// *button
// *slider
// *text field

// --- math ---
// *vector ops
// *matrix
// *bezier curve
// *catmullrom

//* network
//* sound wav, flac, mp3

//--- Input ---
//* keyboard & mouse
//* microphone
//* webcam

//---Editor---
//* highlight err'd line




#include "dexgel\dexgel.hpp"

#include "objLoader.h"

#include <vector>
#include <cmath>
#include <d3dx9.h>

#include <glm/gtc/matrix_projection.hpp>
#include <glm/gtc/matrix_transform.hpp>

// Squirrel 
#include <squirrel.h> 
#include <sqstdio.h> 
#include <sqstdmath.h> 
#include <sqstdaux.h> 


#ifdef _MSC_VER
#pragma comment (lib ,"squirrel.lib")
#pragma comment (lib ,"sqstdlib.lib")
#endif

#ifdef SQUNICODE 

#define scvprintf vfwprintf
#else 

#define scvprintf vfprintf
#endif 
// end squirrel headers

float frameDelta = 0.f;
std::vector< dexgel::LitVertex > vertBuf;
std::vector< dexgel::SVertex > tooDeeVerts;
dexgel::Rendrer *g3D = NULL;

#define DEG2RAD(x) (x * (D3DX_PI / 180.0f)) // Converts degrees to radians
#define RAD2DEG(x) (x * (180.0f / D3DX_PI)) // Converts radians to degrees


#pragma comment(lib, "dexgel.lib")


void cylinder(float u,float v, float &x, float &y, float &z) 
{
	float s = u*6.28f;
	float t = v*3.14f;

	x = cos(s);
	y = cos(t);
	z = sin(s);
}

void kleinbottle(float p1, float p2, float &x, float &y, float &z)
{
	float u = p1 * 2*3.14f;
	float v = p2 * 2.0f*3.14f;

	float r = 4.f*(1.f-cos(u)*.5f);

	x=0.f;
	y=0.f;
	if (u >= 0 && u < 3.14f ){
		x = 6.f * cos(u) * (1.f+sin(u)) + r*cos(u)*cos(v);  
		y = 16.f * sin(u) + r * sin(u)*cos(v);
	}
	else{
		x = 6.f * cos(u) * (1.f+sin(u)) + r*cos(v+3.14f);
		y = 16.f * sin(u);
	}



	z=r*sin(v);
}
void torus(float p1, float p2, float &x, float &y, float &z)
{
	float u=p1 * 2*3.14f;
	float v=p2 * 2*3.14f;
	float bigR = 1;
	float r = .5;
	x = (bigR+r*cos(v)) * cos(u) ;
	y = (bigR+r*cos(v)) * sin(u);  
	z = r*sin(v);
}

void sphere(float p1, float p2, float &x, float &y, float &z, float r)
{
	float u = p1 * 6.28f;
	float v = p2 * 3.14f;
	x = r * cos(u) * sin(v);
	y = r * sin(u) * sin(v);
	z = r * cos(v);
}



void glVertex(float x, float y, int red, int green, int blue)
{
	dexgel::SVertex v = {x , y , 0.f, D3DCOLOR_ARGB(255, red, green, blue) };
	tooDeeVerts.push_back( v );
}

bool LockFrameRate(int frame_rate = 60);


void printfunc(HSQUIRRELVM v,const SQChar *s,...)
{
	va_list vl;
	va_start(vl, s);
	scvprintf(stdout, s, vl);
	va_end(vl);
}

void errorfunc(HSQUIRRELVM v,const SQChar *s,...)
{
	va_list vl;
	va_start(vl, s);
	scvprintf(stderr, s, vl);
	va_end(vl);
}

SQInteger register_global_func(HSQUIRRELVM v,SQFUNCTION f,const char *fname)
{
	sq_pushroottable(v);
	sq_pushstring(v,fname,-1);
	sq_newclosure(v,f,0); //create a new function
	sq_createslot(v,-3);
	sq_pop(v,1); //pops the root table
	return 0;
}

SQInteger drawline(HSQUIRRELVM v)
{
	SQInteger nargs = sq_gettop(v); //number of arguments

	assert( nargs == 5 );

	float x1,y1,x2,y2;
	sq_getfloat(v,2,&x1);
	sq_getfloat(v,3,&y1);
	sq_getfloat(v,4,&x2);
	sq_getfloat(v,5,&y2);

	glVertex(x1,y1,255,255,255);
	glVertex(x2,y2,255,255,255);

	return 0; // no ret vals
}

SQInteger print_args(HSQUIRRELVM v)
{

	SQInteger nargs = sq_gettop(v); //number of arguments

	for(SQInteger n=1;n<=nargs;n++)
	{

		printf("arg %d is ",n);
		switch(sq_gettype(v,n))
		{
		case OT_NULL:
			printf("null");
			break;
		case OT_INTEGER:
			printf("integer");
			break;
		case OT_FLOAT:
			printf("float");
			break;
		case OT_STRING:
			printf("string");
			break;
		case OT_TABLE:
			printf("table");
			break;
		case OT_ARRAY:
			printf("array");
			break;
		case OT_USERDATA:
			printf("userdata");
			break;
		case OT_CLOSURE:
			printf("closure(function)");
			break;
		case OT_NATIVECLOSURE:
			printf("native closure(C function)");
			break;
		case OT_GENERATOR:
			printf("generator");
			break;
		case OT_USERPOINTER:
			printf("userpointer");
			break;
		default:
			return sq_throwerror(v,"invalid param"); //throws an exception
		}
		printf("\n");

	}


	sq_pushinteger(v,nargs); //push the number of arguments as return value

	printf("hello from c++\n");
	return 1; //1 because 1 value is returned
}

void call_foo(HSQUIRRELVM v, int n,float f,const SQChar *s)
{
	SQInteger top = sq_gettop(v); //saves the stack size before the call
	sq_pushroottable(v); //pushes the global table
	sq_pushstring(v,_SC("foo"),-1);
	if(SQ_SUCCEEDED(sq_get(v,-2))) { //gets the field 'foo' from the global table
		sq_pushroottable(v); //push the 'this' (in this case is the global table)
		sq_pushinteger(v,n); 
		sq_pushfloat(v,f);
		sq_pushstring(v,s,-1);
		sq_call(v,4,SQFalse,SQTrue); //calls the function 
	}
	sq_settop(v,top); //restores the original stack size
}

void WriteTriangle(glm::vec3 &v0, glm::vec3 &v1, glm::vec3 &v2, glm::vec3 &n, glm::vec3 &color)
{
	//int re = int(128.f+128.f*n.x);
	//int gr = int(128.f+128.f*n.y);
	//int bl = int(128.f+128.f*n.z);
	int re = int(255.f * color.x);
	int gr = int(255.f * color.y);
	int bl = int(255.f * color.z);
	dexgel::LitVertex vert0 = { v0.x,v0.y,v0.z,n.x,n.y,n.z,D3DCOLOR_ARGB(255, re,gr,bl) };
	dexgel::LitVertex vert1 = { v1.x,v1.y,v1.z,n.x,n.y,n.z,D3DCOLOR_ARGB(255, re,gr,bl) };
	dexgel::LitVertex vert2 = { v2.x,v2.y,v2.z,n.x,n.y,n.z,D3DCOLOR_ARGB(255, re,gr,bl) };
	vertBuf.push_back(  vert0  );
	vertBuf.push_back(  vert1  );
	vertBuf.push_back(  vert2  );
}

void drawsphere(glm::vec3 pos, glm::vec3 color, float radius)
{
	std::vector< dexgel::SVertex > vertBuf;
	
#define dim1 12
#define dim2 12

	float px[dim1][dim2];
	float py[dim1][dim2];
	float pz[dim1][dim2];
	//float color[dim1][dim2];

	float deltau = 1.0f / (dim1-1.0f);
	float deltav = 1.0f / (dim2-1.0f);  
	float startu = 0.0f;
	float startv = 0.0f;  

	for(int i=0; i<dim1; i++)
	for(int j=0; j<dim2; j++)
	{     
		float u = startu + i * deltau;
		float v = startv + j * deltav;
		float x,y,z;
		sphere(u,v,x,y,z, radius);
		px[i][j] = x; py[i][j] = y; pz[i][j] = z;
	}

	int r = (int) (255 * color.x);
	int g = (int) (255 * color.y);
	int b = (int) (255 * color.z);

	for(int i=0; i<dim1-1; i++)
	{
		float sc = 2;
		for(int j=0; j<dim2-1; j++)  
		{

			dexgel::SVertex v0 = {px[i][j], py[i][j], pz[i][j], D3DCOLOR_XRGB(r,g,b) };
			dexgel::SVertex v1 = {px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1], D3DCOLOR_XRGB(r,g,b) };
			dexgel::SVertex v2 = {px[i][j+1], py[i][j+1], pz[i][j+1], D3DCOLOR_XRGB(r,g,b) };


			dexgel::SVertex v3 = {px[i][j], py[i][j], pz[i][j],D3DCOLOR_XRGB(r,g,b)};
			dexgel::SVertex v4 = {px[i+1][j], py[i+1][j], pz[i+1][j], D3DCOLOR_XRGB(r,g,b) };
			dexgel::SVertex v5 = {px[i+1][j+1], py[i+1][j+1], pz[i+1][j+1], D3DCOLOR_XRGB(r,g,b) };

			vertBuf.push_back( v0 );
			vertBuf.push_back( v1 );
			vertBuf.push_back( v2 );
			vertBuf.push_back( v3 );
			vertBuf.push_back( v4 );
			vertBuf.push_back( v5 );
		} 
	}

	glm::mat4 identity(1.0f);
	glm::mat4 mWorld = glm::translate(identity, pos );

	assert( g3D );
	g3D->SetTransform( (float*) (&mWorld[0]) );

	if ( !g3D->render(  &vertBuf[0], vertBuf.size()/3 )) // Draw our triangle
	{
		bool fail = true;
	}
}

int main(int argc, char* argv[])
{
	HSQUIRRELVM v; 
	v = sq_open(1024); // creates a VM with initial stack size 1024 

	//REGISTRATION OF STDLIB
	sq_pushroottable(v); //push the root table where the std function will be registered
	//sqstd_register_iolib(v);  //registers a library
	sqstd_register_mathlib(v);  
	// ... call here other stdlibs string,math etc...
	sq_pop(v,1); //pops the root table
	//END REGISTRATION OF STDLIB

	sqstd_seterrorhandlers(v); //registers the default error handlers

	sq_setprintfunc(v, printfunc,errorfunc); //sets the print function

	sq_pushroottable(v); //push the root table(were the globals of the script will be stored)

	register_global_func(v, &print_args, "prt");
	register_global_func(v, &drawline, "drawline");


	bool isScriptRunnable = SQ_SUCCEEDED(sqstd_dofile(v, _SC("test.nut"), SQFalse, SQTrue));

	objLoader *objData = new objLoader();
	objData->load("test.obj");
	
	printf("Number of vertices: %i\n", objData->vertexCount);
	printf("Number of vertex normals: %i\n", objData->normalCount);
	printf("Number of texture coordinates: %i\n", objData->textureCount);
	printf("\n");

	printf("Number of faces: %i\n", objData->faceCount);
	for(int i=0; i<objData->faceCount; i++)
	{
		obj_face *o = objData->faceList[i];
		
		obj_vector *v0 = (objData->vertexList[ o->vertex_index[0] ]);
		obj_vector *v1 = (objData->vertexList[ o->vertex_index[1] ]);
		obj_vector *v2 = (objData->vertexList[ o->vertex_index[2] ]);
		
		glm::vec3 a = glm::vec3( (float)v0->e[0], (float)v0->e[1], (float)v0->e[2]  );
		glm::vec3 b = glm::vec3( (float)v1->e[0], (float)v1->e[1], (float)v1->e[2]  );
		glm::vec3 c = glm::vec3( (float)v2->e[0], (float)v2->e[1], (float)v2->e[2]  );
		glm::vec3 ab = b-a;
		glm::vec3 ac = c-a;
		glm::vec3 normal = glm::normalize( glm::cross( ab, ac ) );
		
		obj_material *mat = objData->materialList[ objData->faceList[i]->material_index ];
		glm::vec3 color = glm::vec3( mat->diff[0], mat->diff[1], mat->diff[2] );

		WriteTriangle(a,b,c,normal,color);

		if ( objData->faceList[i]->vertex_count == 4 )
		{
			obj_vector *v0 = (objData->vertexList[ o->vertex_index[0] ]);
			obj_vector *v2 = (objData->vertexList[ o->vertex_index[2] ]);
			obj_vector *v3 = (objData->vertexList[ o->vertex_index[3] ]);
			
			glm::vec3 a = glm::vec3( (float)v0->e[0], (float)v0->e[1], (float)v0->e[2]  );
			glm::vec3 b = glm::vec3( (float)v2->e[0], (float)v2->e[1], (float)v2->e[2]  );
			glm::vec3 c = glm::vec3( (float)v3->e[0], (float)v3->e[1], (float)v3->e[2]  );
			glm::vec3 ab = b-a;
			glm::vec3 ac = c-a;
			glm::vec3 normal = glm::normalize( glm::cross( ab, ac ) );
			WriteTriangle(a,b,c, normal, color);
		}

	}

	delete(objData);

	float xres = 600;
	float yres = 600;
	dexgel::Window App( (int)xres, (int)yres, "TorApp");

	std::vector< dexgel::SVertex > pts;
	int segs = 8;
	for(int i=0; i<segs; i++)
	{
		float ang = 6.28f * (float)i / (float) (segs-1);
		float x = 100.0f + cos( ang ) * 50.0f;
		float y = 100.0f + sin( ang ) * 50.0f;
		dexgel::SVertex pt = {x, y, 1.0f, D3DCOLOR_ARGB(255, 255, 255, 255) };

		float ang2 = 6.28f * (float)(i+1) / (float) (segs-1);
		float x2 = 100.0f + cos( ang2 ) * 50.0f;
		float y2 = 100.0f + sin( ang2 ) * 50.0f;
		dexgel::SVertex pt2 = {x2, y2, 1.0f, D3DCOLOR_ARGB(255, 255, 255, 255) };
		pts.push_back( pt2 );
	}


	//vertBuf.clear();
	tooDeeVerts.clear();





			int numframes = 0;

			glm::vec3 cam = glm::vec3(0.f, 0.f, -5.f);
			glm::vec3 targ = glm::vec3(0.f, 0.f, 0.f);

			float oldmousx = 0.f;
			float oldmousy = 0.f;

			while (App.IsOpened())
			{
				
				float mousx = (float) App.getMouseX();
				float mousy = (float) App.getMouseY();

				static float hang = 0.f;
				float mouseSpeedX = (mousx - oldmousx) * 0.01f;

				oldmousx = mousx;
				oldmousy = mousy;

				if (App.mouseDown() )
					hang += mouseSpeedX;
				glm::vec3 dirvec = glm::vec3( sin(hang), 0.f, cos(hang) );

				float speed = 0.1;

				if (App.keyDown('A'))
				{
					cam += glm::vec3( sin(hang-1.57f), 0.f, cos(hang-1.57f) ) * speed;
				}
				if (App.keyDown('D'))
				{
					cam += glm::vec3( sin(hang+1.57f), 0.f, cos(hang+1.57f) ) * speed;
				}
				if (App.keyDown('W'))
				{
					cam += dirvec * speed;
				}
				if (App.keyDown('S'))
				{
					cam -= dirvec * speed;
				}
				if (App.keyDown('Q'))
				{
					cam += glm::vec3(0.f, 0.5f, 0.f) * speed;
				}
				if (App.keyDown('E'))
				{
					cam += glm::vec3(0.f, -0.5f, 0.f) * speed;
				}

				if (App.keyDown('R') )
				{
					isScriptRunnable = SQ_SUCCEEDED(sqstd_dofile(v, _SC("test.nut"), SQFalse, SQTrue));
					if (isScriptRunnable)
					{
						printf("re-loaded script - no errors");
					}else{
						printf("re-loaded script - has errors and can't run");
					}
				}

				targ = cam + dirvec;

				App.UpdateMessages();

				if (App.IsOpened() && App.canRender() )
				{
					if( isScriptRunnable ) // also prints syntax errors if any 
					{
						call_foo(v,numframes,2.5,_SC("hi"));
					}


					g3D = App.getRendrer();

					g3D->begin(); // Begin the scene
					g3D->enableLighting();
					g3D->clearColor(D3DCOLOR_ARGB(255, 0, 64, 0)); // Clear our viewport to black

					float xang = mousx/xres * 6.28f;
					float yang = mousy/yres * 6.28f;
					glm::vec3 gLightPos = glm::vec3( cos(xang)*2.f,2.f,sin(xang)*2.f);

					

					g3D->setLightPos(0, gLightPos); // Set the light position
					g3D->setLightType(0, dexgel::LIGHT_POINT);
					g3D->setLightActive(0, true); // Make the light active
					// **NOTE** You must do this to update the
					// D3D light's data	


					g3D->setProjMatrix( 60.0f * 3.14f / 180.f, xres/yres, 1.0f, 8192.0f );
					g3D->setViewMatrix( cam, targ );



					glm::mat4 identity = glm::mat4(1.0);			
					//glm::mat4 mRotx = glm::rotate( identity, RAD2DEG(xang), glm::vec3(1.f, 0.f, 0.f) );
					//glm::mat4 mRoty = glm::rotate( identity, RAD2DEG(yang), glm::vec3(0.f, 1.f, 0.f) );
					glm::mat4 mRotx = glm::rotate( identity, 90.f, glm::vec3(1.f, 0.f, 0.f) );
					glm::mat4 mWorld = mRotx; //mRoty * mRotx;


					g3D->SetTransform( (float*) (&mWorld[0]) );

					if ( !g3D->render(  &vertBuf[0], vertBuf.size()/3 )) // Draw our triangle
					{
						bool fail = true;
					}

					g3D->disableLighting();
					drawsphere( gLightPos, glm::vec3(1.f, 1.f, 1.f), 0.1f );

					// 2D
					g3D->setViewMatrix( (float*) &identity[0] );
					g3D->SetTransform( (float*) (&identity[0]) );

					glm::mat4 orthoProj = glm::ortho(0.f,xres,yres,0.f, -1.0f, 1.0f );
					g3D->setProjOrtho(  (float*) (&orthoProj[0]) );

					if (tooDeeVerts.size() > 0 )
					{
						g3D->renderLine( &tooDeeVerts[0], tooDeeVerts.size() );
						tooDeeVerts.clear(); // ready for next frame
					}

					g3D->end(); // End the scene and blit

				}else{
					Sleep(1);
				}




				numframes++;

			}


			sq_pop(v,1); //pops the root table
			sq_close(v); 

			return EXIT_SUCCESS;
}


// Locks the frame rate at "frame_rate"
// Returns true when it's okay to draw, false otherwise
bool LockFrameRate(int frame_rate)
{
	static float lastTime = 0.0f;

	// Get current time in seconds (milliseconds * .001 = seconds)
	float currentTime = GetTickCount() * 0.001f; 
	frameDelta = currentTime - lastTime;

	// Get the elapsed time by subtracting the current time from the last time
	// If the desired frame rate amount of seconds has passed -- return true (ie Blit())
	if((frameDelta) > (1.0f / frame_rate))
	{
		// Reset the last time
		lastTime = currentTime;	
		return true;
	}

	return false;
}